﻿/******************************************************************************
 *
 * @copyright:          Copyright 2011 UniSky Ltd.
 * @file:
 * @author:              kenping.liu
 * @creation:
 * @description:
 *
 * @modifications
 * date         user                description
 *
 *
 * @more-details
 *
 *****************************************************************************/

#include "u/u_xml.h"
#include "u/u_logger.h"
#include "u/u_io.h"

U_NAMESPACE_BEGIN(u)
U_NAMESPACE_BEGIN(xml)

Helper::Entity Helper::entities[NUM_ENTITY] =
{
    { "&amp;",  5, '&' },
    { "&lt;",   4, '<' },
    { "&gt;",   4, '>' },
    { "&quot;", 6, '\"' },
    { "&apos;", 6, '\'' },
    { "&#x0D;", 6, '\r' },
    { "&#x0A;", 6, '\n' }
};

bool Helper::load(Element& root, const char* filename, bool utf8)
{
    u::Log::trace("load xml file: %s", filename);

    root.clear();

    std::string content;
    u::AutoHeap tmp(10);
    if ( u::io::File::read(tmp, filename) )
    {
        content.append(tmp.head(), tmp.size());
        if ( utf8 )
        {
            u::AutoHeap tmp;
            u::util::Str::utf8_to_a(tmp, content.c_str(), content.size());

            content.erase();
            content.append(tmp.head(), tmp.size());
        }
        parse(root, content);
    }

    return !root.name.empty();
}

void Helper::parse(Element& node, const std::string& strXml)
{
    const TCHAR COPEN          = _T('<');
    const TCHAR CCLOSE         = _T('>');
    const TCHAR CSLASH         = _T('/');
    const TCHAR CSPACE         = _T(' ');
    const TCHAR CQUOTE         = _T('\'');
    const TCHAR CDQUOTE        = _T('\"');
    const TCHAR CEQUAL         = _T('=');
    const TCHAR CNEW           = _T('\n');
    const TCHAR CTAB           = _T('\t');
    const TCHAR CEXCLAMATION   = _T('!');
    const TCHAR CMINUS         = _T('-');
#if defined(WINDOWS)
    const TCHAR CSQRPO         = _T('[');
#endif
    const TCHAR CSQRPC         = _T(']');
    const TCHAR SZCDATA[9]     = _T("![CDATA[");
    const TCHAR CQM            = _T('?');
    const TCHAR CRET           = 13;//carriage return

    std::string::size_type k        = 0;    // cursor
    std::string::size_type j        = 0;    //  second level cursor;
    bool                   is_CDATA = false;
    bool                   is_PI    = false;
    TCHAR                  c        = 0;
    TCHAR                  delim    = 0;

    std::string            e_name;
    std::string            e_value;
    std::string            a_name;
    std::string            a_value;

    Element* current = &node;

    while ( k < strXml.size() )
    {
        c = strXml[k];
        if(c == CNEW || c == CTAB || c == CRET)
        {
            k++;
            continue;
        }

        if ( c==COPEN )
        {
#if defined(WINDOWS)
#pragma region This section looks for a comment
#endif
            if(strXml[k+1] == CEXCLAMATION && strXml[k+2] == CMINUS && strXml[k+3] == CMINUS)//it is a comment
            {
                // <!-- ........ -->
                // Comment section
                k += 4;
                c = strXml[k];
                while ( !(strXml[k] == CMINUS && strXml[k+1] == CMINUS && strXml[k+2] == CCLOSE) )
                {
                    c = strXml[++k];
                }
                k += 3;
                // drop comments
                continue;
            }

#if defined(WINDOWS)
#pragma endregion
#pragma region This section looks for a CDATA section
#endif
            // <![CDATA[ .......... ]]>
            while ( (k+10)<strXml.size() && j<9 )
            {
                if ( strXml[k+1+j]!=SZCDATA[j] )
                {
                    break;
                }
                j++;
                if ( j==8 )
                {
                    //definetly a CDATA section follows
                    k = k + j;
                    std::string::size_type start = k;
                    while((k + 3) < strXml.size() && strXml[k+1] != CSQRPC && strXml[k+2] != CSQRPC && strXml[k+3] != CCLOSE)
                    {
                        k++;
                    }
                    std::string::size_type stop = k;
                    k += 5;
                    current->value = strXml.substr(start, stop - start);
                    j = 0;
                    is_CDATA = true;
                    break;
                }
            }
            if(is_CDATA)
            {
                is_CDATA = false;
                continue;
            }

#if defined(WINDOWS)
#pragma endregion CDATA
#endif

            e_name.erase();
            if ( strXml[k+1]==CSLASH )
            {
                //closing tag for the last opend node
                current = current->parent;
                k++;
                while ( strXml[k] != CCLOSE )
                {
                    k++;
                }
                k++;
                continue;
            }
            if ( strXml[k+1] == CQM )
            {
                c = strXml[++k];
                is_PI = true;
            }

            // open tag. It means we have a node so we create it
            c = strXml[++k];
            while ( c!=CSLASH && c!=CSPACE && c!=CCLOSE )
            {
                //loops until the node name has been entirely read
                if( c!=CNEW && c!=CTAB && c!=CRET)
                {
                    e_name.append(&c, 1);
                }
                c = strXml[++k];
            }

            // we have seted this node, navigate to a child of it
            if ( current != NULL && !current->name.empty() )
            {
                current->children.push_back(Element("", current));
                current = &current->children[current->children.size()-1];
            }

            current->name = e_name;
            while ( c==CSPACE )
            {
                c = strXml[++k];
                if( c==CSLASH || c==CCLOSE || ( c==CQM && is_PI ) )
                {
                    break;
                }
                
                a_name.erase();
                a_value.erase();

                while ( c!=CEQUAL)
                {
                    //loops until the attribute name has been entirely read
                    if( c!=CNEW && c!=CTAB && c!=CRET)
                    {
                        a_name.append(&c, 1);
                    }
                    c = strXml[++k];
                }
                c = strXml[++k];
                if ( c==CQUOTE || c==CDQUOTE )
                {
                    delim = c;
                    c = strXml[++k];
                }
                while ( c!=delim && delim!=0 )
                {
                    //loops until the attribute value has been entirely read
                    if ( c!=CNEW && c!=CTAB && c!=CRET )
                    {
                        a_value.append(&c, 1);
                    }
                    c = strXml[++k];
                }
                delim = 0;
                c = strXml[++k];
                u::util::Str::trim(a_name);
                a_value = decode(a_value);
                current->set_attribute(a_name, a_value.c_str());
            }

            if ( c==CSLASH )
            {
                current = current->parent;
                c = strXml[++k];
                while ( c!=CCLOSE )
                {
                    c = strXml[++k];
                }
            }

            if ( c==CQM && is_PI )
            {
                //Current->SetNodeAsPI();
                //Current = Current->GetParent();
                c = strXml[++k];
                is_PI = false;
                while ( k<strXml.size() && c!=CCLOSE )
                {
                    c = strXml[++k];
                }

                // .....
                current->name.erase();
                current->value.erase();
                current->children.clear();
                current->attributes.clear();
            }

            if ( c==CCLOSE )
            {
            }
        }

        if ( c!=COPEN && c!=CCLOSE && c!=CSLASH/* && c!=CSPACE*/ )
        {
            e_value.erase();
            while ( k<strXml.size() && c!=COPEN )
            {
                if ( c!=CNEW && c!=CTAB && c!=CRET/* && c != CSPACE*/ )
                {
                    e_value.append(&c, 1);
                }
                c = strXml[++k];
            }
            if ( NULL!=current && current->value.empty())
            {
                u::util::Str::trim(e_value);
                current->value = decode(e_value);
            }
            continue;
        }
        k++;
    }
    arrange(&node);
}

bool Helper::save(Element& root, const char* filename, bool utf8, const char* present)
{
    std::string output;
    if ( root.name.empty() )
    {
        root.name = "root";
    }
    build(output, root);

    if ( NULL!=present )
    {
        output.insert(0, present);
    }
    if ( utf8 )
    {
        u::AutoHeap tmp;
        u::util::Str::a_to_utf8(tmp, output.c_str(), output.size());
        return u::io::File::write(filename, tmp.head(), tmp.size());
    }

    return u::io::File::write(filename, output.c_str(), output.size());
};

Element* Helper::get_child(Element* node, const std::string& name, bool create_new)
{
    for ( size_t i=0; i<node->children.size(); i++ )
    {
        if ( node->children[i].name==name )
        {
            return &node->children[i];
        }
    }

    if ( create_new )
    {
        Element child("", node);
        child.name  = name;
        child.value = "";
        node->children.push_back(child);
        return &node->children[node->children.size()-1];
    }

    return NULL;
}

Element* Helper::query_by_path(Element* node, const std::string& path, bool create_new)
{
    if ( NULL!=node )
    {
        std::string::size_type pos = path.find("/");
        if ( std::string::npos==pos )
        {
            return get_child(node, path, create_new);
        }
        else
        {
            Element* child = get_child(node, path.substr(0, pos), create_new);
            return query_by_path(child, path.substr(pos+1), create_new);
        }
    }
    return NULL;
}

Element* Helper::query_child(Element* node, const Attribute* attrs, int count, bool create_new, const std::string& child_name)
{
    for ( size_t i=0; i<node->children.size(); i++ )
    {
        Element& child = node->children[i];
        bool matched = true;
        for ( int p=0; matched && p<count; p++ )
        {
            std::string tmp;
            matched = (query_attribute(tmp, &child, attrs[p].name) && tmp==attrs[p].value);
        }

        if ( matched )
        {
            return &child;
        }
    }

    if ( create_new )
    {
        Element child("", node);
        child.name = child_name;
        for ( int p=0; p<count; p++ )
        {
            child.attributes.push_back(attrs[p]);
        }
        node->children.push_back(child);
        return &node->children[node->children.size()-1];
    }

    return NULL;
}

bool Helper::query_attribute(std::string& value, const Element* node, const std::string& name)
{
    for ( size_t i=0; i<node->attributes.size(); i++ )
    {
        if ( node->attributes[i].name==name )
        {
            value = node->attributes[i].value;
            return true;
        }
    }
    return false;
}

int Helper::get_attribute_int(const Element* node, const std::string& name, int init_v)
{
    std::string tmp;
    int ret = init_v;
    if ( query_attribute(tmp, node, name) )
    {
        u::util::Str::from(tmp, &ret);
    }
    return ret;
}

UINT Helper::get_attribute_uint(const Element* node, const std::string& name, UINT init_v)
{
    std::string tmp;
    UINT ret = init_v;
    if ( query_attribute(tmp, node, name) )
    {
        u::util::Str::from(tmp, &ret);
    }
    return ret;
}

bool Helper::get_attribute_bool(Element* node, const std::string& name, bool init_v)
{
    std::string tmp;
    bool ret = init_v;
    if ( query_attribute(tmp, node, name) )
    {
        ret = ((0==u_stricmp("true", tmp.c_str())) || 0!=atoi(tmp.c_str()));
    }
    else
    {
        node->set_attribute(name, (int)init_v);
    }
    return ret;
}

std::string Helper::get_attribute_string(const Element* node, const std::string& name, const std::string& init_v)
{
    std::string tmp(init_v);
    query_attribute(tmp, node, name);
    return tmp;
}

Element* Helper::query_node(Element* node, const std::string& name)
{
    Element* result = NULL;
    if ( node->name==name )
    {
        result = node;
    }
    else
    {
        for ( size_t i=0; NULL==result && i<node->children.size(); i++ )
        {
            result = query_node(&node->children[i], name);
        }
    }
    return result;
}

void Helper::build(std::string& output, Element& node, const std::string& indent)
{
    output.append(indent).append("<").append(node.name);
    A_Iter ita = node.attributes.begin();
    for ( ; ita!=node.attributes.end(); ita++ )
    {
        output.append(" ").append(ita->name).append("=\"");
        output.append(encode(ita->value)).append("\"");
    }
    if ( node.children.empty() )
    {
        if ( node.value.empty() )
        {
            output.append(" />\r\n");
        }
        else
        {
            output.append(">").append(encode(node.value));
            output.append("</").append(node.name).append(">\r\n");
        }
    }
    else
    {
        output.append(">\r\n");

        if ( !node.value.empty() )
        {
            output.append(indent).append("    ").append(encode(node.value)).append("\r\n");
        }

        E_Iter ite = node.children.begin();
        std::string child_indent("    ");
        child_indent.append(indent);
        for ( ; ite!=node.children.end(); ite++ )
        {
            build(output, *ite, child_indent);
        }
        output.append(indent).append("</").append(node.name).append(">\r\n");
    }
}

std::string Helper::encode(const std::string& _Value)
{
    std::string str(_Value);
    for ( int i=0; i<NUM_ENTITY; i++ )
    {
        for ( std::string::size_type pos=0; pos<str.size(); )
        {
            if ( str[pos] == entities[i].chr )
            {
                str.replace(pos, 1, entities[i].str, entities[i].length);
                pos += entities[i].length;
            }
            else
            {
                pos++;
            }
        }
    }
    return str;
}

std::string Helper::decode(const std::string& _Value)
{
    std::string str(_Value);
    for ( int i=(NUM_ENTITY-1); i>-1; i-- )
    {
        std::string::size_type pos=0;
        while ( std::string::npos != (pos=str.find(entities[i].str, pos)) )
        {
            str.replace(pos, entities[i].length, &entities[i].chr, 1);
            pos++;
        }
    }
    return str;
}

void Helper::arrange(Element* node)
{
    u::util::Str::trim(node->value);
    if ( !node->children.empty() )
    {
        for ( E_Iter it=node->children.begin(); it!=node->children.end(); it++ )
        {
            it->parent = node;
            arrange(&(*it));
        }
    }
}

U_NAMESPACE_END(xml)
U_NAMESPACE_END(u)
